/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.frameworkset.commons.dbcp;

import java.io.ByteArrayInputStream;
import java.sql.Connection;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.RefAddr;
import javax.naming.Reference;
import javax.naming.spi.ObjectFactory;
import javax.sql.DataSource;

import com.frameworkset.common.poolman.PoolManConstants;

/**
 * <p>JNDI object factory that creates an instance of
 * <code>BasicDataSource</code> that has been configured based on the
 * <code>RefAddr</code> values of the specified <code>Reference</code>,
 * which must match the names and data types of the
 * <code>BasicDataSource</code> bean properties.</p>
 *
 * @author Craig R. McClanahan
 * @author Dirk Verbeeck
 * @version $Revision: 491655 $ $Date: 2007-01-01 15:05:30 -0700 (Mon, 01 Jan 2007) $
 */
public class BasicDataSourceFactory implements ObjectFactory {

    private final static String PROP_DEFAULTAUTOCOMMIT = "defaultAutoCommit";
    private final static String PROP_DEFAULTREADONLY = "defaultReadOnly";
    private final static String PROP_DEFAULTTRANSACTIONISOLATION = "defaultTransactionIsolation";
    private final static String PROP_DEFAULTCATALOG = "defaultCatalog";
    private final static String PROP_DRIVERCLASSNAME = "driverClassName";
    private final static String PROP_MAXACTIVE = "maxActive";
    private final static String PROP_MAXIDLE = "maxIdle";
    private final static String PROP_MINIDLE = "minIdle";
    private final static String PROP_INITIALSIZE = "initialSize";
    private final static String PROP_MAXWAIT = "maxWait";
    private final static String PROP_TESTONBORROW = "testOnBorrow";
    private final static String PROP_TESTONRETURN = "testOnReturn";
    private final static String PROP_TIMEBETWEENEVICTIONRUNSMILLIS = "timeBetweenEvictionRunsMillis";
    private final static String PROP_NUMTESTSPEREVICTIONRUN = "numTestsPerEvictionRun";
    private final static String PROP_MINEVICTABLEIDLETIMEMILLIS = "minEvictableIdleTimeMillis";
    private final static String PROP_TESTWHILEIDLE = "testWhileIdle";
    private final static String PROP_PASSWORD = "password";
    private final static String PROP_URL = "url";
    private final static String PROP_USERNAME = "username";
    private final static String PROP_VALIDATIONQUERY = "validationQuery";
    private final static String PROP_ACCESSTOUNDERLYINGCONNECTIONALLOWED = "accessToUnderlyingConnectionAllowed";
    private final static String PROP_REMOVEABANDONED = "removeAbandoned";
    private final static String PROP_REMOVEABANDONEDTIMEOUT = "removeAbandonedTimeout";
    private final static String PROP_LOGABANDONED = "logAbandoned";
    private final static String PROP_POOLPREPAREDSTATEMENTS = "poolPreparedStatements";
    private final static String PROP_MAXOPENPREPAREDSTATEMENTS = "maxOpenPreparedStatements";
    private final static String PROP_CONNECTIONPROPERTIES = "connectionProperties";
    /**
     * Added by biaoping.yin on 2007-7-22
     */
    private final static String PROP_WHENEXHAUSTEDACTION = "whenExhaustedAction";

    private final static String[] ALL_PROPERTIES = {
        PROP_DEFAULTAUTOCOMMIT,
        PROP_DEFAULTREADONLY,
        PROP_DEFAULTTRANSACTIONISOLATION,
        PROP_DEFAULTCATALOG,
        PROP_DRIVERCLASSNAME,
        PROP_MAXACTIVE,
        PROP_MAXIDLE,
        PROP_MINIDLE,
        PROP_INITIALSIZE,
        PROP_MAXWAIT,
        PROP_TESTONBORROW,
        PROP_TESTONRETURN,
        PROP_TIMEBETWEENEVICTIONRUNSMILLIS,
        PROP_NUMTESTSPEREVICTIONRUN,
        PROP_MINEVICTABLEIDLETIMEMILLIS,
        PROP_TESTWHILEIDLE,
        PROP_PASSWORD,
        PROP_URL,
        PROP_USERNAME,
        PROP_VALIDATIONQUERY,
        PROP_ACCESSTOUNDERLYINGCONNECTIONALLOWED,
        PROP_REMOVEABANDONED,
        PROP_REMOVEABANDONEDTIMEOUT,
        PROP_LOGABANDONED,
        PROP_POOLPREPAREDSTATEMENTS,
        PROP_MAXOPENPREPAREDSTATEMENTS,
        PROP_CONNECTIONPROPERTIES
    };

    // -------------------------------------------------- ObjectFactory Methods

    /**
     * <p>Create and return a new <code>BasicDataSource</code> instance.  If no
     * instance can be created, return <code>null</code> instead.</p>
     *
     * @param obj The possibly null object containing location or
     *  reference information that can be used in creating an object
     * @param name The name of this object relative to <code>nameCtx</code>
     * @param nameCtx The context relative to which the <code>name</code>
     *  parameter is specified, or <code>null</code> if <code>name</code>
     *  is relative to the default initial context
     * @param environment The possibly null environment that is used in
     *  creating this object
     *
     * @exception Exception if an exception occurs creating the instance
     */
    public Object getObjectInstance(Object obj, Name name, Context nameCtx,
                                    Hashtable environment)
        throws Exception {

        // We only know how to deal with <code>javax.naming.Reference</code>s
        // that specify a class name of "javax.sql.DataSource"
        if ((obj == null) || !(obj instanceof Reference)) {
            return null;
        }
        Reference ref = (Reference) obj;
        if (!"javax.sql.DataSource".equals(ref.getClassName())) {
            return null;
        }

        Properties properties = new Properties();
        for (int i = 0 ; i < ALL_PROPERTIES.length ; i++) {
            String propertyName = ALL_PROPERTIES[i];
            RefAddr ra = ref.get(propertyName);
            if (ra != null) {
                String propertyValue = ra.getContent().toString();
                properties.setProperty(propertyName, propertyValue);
            }
        }

        return createDataSource(properties);
    }

    /**
     * Creates and configures a {@link BasicDataSource} instance based on the
     * given properties.
     * 
     * @param properties the datasource configuration properties
     * @throws Exception if an error occurs creating the data source
     */
    public static DataSource createDataSource(Properties properties) throws Exception {
    	DataSource ds = null;
    	boolean usepool = Boolean.parseBoolean((String)properties.get(PoolManConstants.PROP_USEPOOL));    	
    	if(usepool)
    	{
	        BasicDataSource dataSource = new BasicDataSource();
	        String value = null;
	        value = properties.getProperty(PROP_DEFAULTAUTOCOMMIT);
	        if (value != null) {
	            dataSource.setDefaultAutoCommit(Boolean.valueOf(value).booleanValue());
	        }
	
	        value = properties.getProperty(PROP_DEFAULTREADONLY);
	        if (value != null) {
	            dataSource.setDefaultReadOnly(Boolean.valueOf(value).booleanValue());
	        }
	
	        value = properties.getProperty(PROP_DEFAULTTRANSACTIONISOLATION);
	        if (value != null) {
	            int level = PoolableConnectionFactory.UNKNOWN_TRANSACTIONISOLATION;
	            if ("NONE".equalsIgnoreCase(value)) {
	                level = Connection.TRANSACTION_NONE;
	            }
	            else if ("READ_COMMITTED".equalsIgnoreCase(value)) {
	                level = Connection.TRANSACTION_READ_COMMITTED;
	            }
	            else if ("READ_UNCOMMITTED".equalsIgnoreCase(value)) {
	                level = Connection.TRANSACTION_READ_UNCOMMITTED;
	            }
	            else if ("REPEATABLE_READ".equalsIgnoreCase(value)) {
	                level = Connection.TRANSACTION_REPEATABLE_READ;
	            }
	            else if ("SERIALIZABLE".equalsIgnoreCase(value)) {
	                level = Connection.TRANSACTION_SERIALIZABLE;
	            }
	            else {
	                try {
	                    level = Integer.parseInt(value);
	                } catch (NumberFormatException e) {
	                    System.err.println("Could not parse defaultTransactionIsolation: " + value);
	                    System.err.println("WARNING: defaultTransactionIsolation not set");
	                    System.err.println("using default value of database driver");
	                    level = PoolableConnectionFactory.UNKNOWN_TRANSACTIONISOLATION;
	                }
	            }
	            dataSource.setDefaultTransactionIsolation(level);
	        }
	
	        value = properties.getProperty(PROP_DEFAULTCATALOG);
	        if (value != null) {
	            dataSource.setDefaultCatalog(value);
	        }
	
	        value = properties.getProperty(PROP_DRIVERCLASSNAME);
	        if (value != null) {
	            dataSource.setDriverClassName(value);
	        }
	
	        value = properties.getProperty(PROP_MAXACTIVE);
	        if (value != null) {
	            dataSource.setMaxActive(Integer.parseInt(value));
	        }
	
	        value = properties.getProperty(PROP_MAXIDLE);
	        if (value != null) {
	            dataSource.setMaxIdle(Integer.parseInt(value));
	        }
	
	        value = properties.getProperty(PROP_MINIDLE);
	        if (value != null) {
	            dataSource.setMinIdle(Integer.parseInt(value));
	        }
	
	        value = properties.getProperty(PROP_INITIALSIZE);
	        if (value != null) {
	            dataSource.setInitialSize(Integer.parseInt(value));
	        }
	
	        value = properties.getProperty(PROP_MAXWAIT);
	        if (value != null) {
	        	long v_ = Long.parseLong(value) * 1000;
	            dataSource.setMaxWait(v_);
	        }
	
	        value = properties.getProperty(PROP_TESTONBORROW);
	        if (value != null) {
	            dataSource.setTestOnBorrow(Boolean.valueOf(value).booleanValue());
	        }
	
	        value = properties.getProperty(PROP_TESTONRETURN);
	        if (value != null) {
	            dataSource.setTestOnReturn(Boolean.valueOf(value).booleanValue());
	        }
	
	        value = properties.getProperty(PROP_TIMEBETWEENEVICTIONRUNSMILLIS);
	        if (value != null) {
	        	long v_ = Long.parseLong(value) * 1000;
	            dataSource.setTimeBetweenEvictionRunsMillis(v_);
	        }
	
	        value = properties.getProperty(PROP_NUMTESTSPEREVICTIONRUN);
	        if (value != null) {
	            dataSource.setNumTestsPerEvictionRun(Integer.parseInt(value));
	        }
	
	        value = properties.getProperty(PROP_MINEVICTABLEIDLETIMEMILLIS);
	        if (value != null) {
	        	long v_ = Long.parseLong(value) * 1000;
	            dataSource.setMinEvictableIdleTimeMillis(v_);
	        }
	
	        value = properties.getProperty(PROP_TESTWHILEIDLE);
	        if (value != null) {
	            dataSource.setTestWhileIdle(Boolean.valueOf(value).booleanValue());
	        }
	
	        value = properties.getProperty(PROP_PASSWORD);
	        if (value != null) {
	            dataSource.setPassword(value);
	        }
	
	        value = properties.getProperty(PROP_URL);
	        if (value != null) {
	            dataSource.setUrl(value);
	        }
	
	        value = properties.getProperty(PROP_USERNAME);
	        if (value != null) {
	            dataSource.setUsername(value);
	        }
	
	        value = properties.getProperty(PROP_VALIDATIONQUERY);
	        if (value != null) {
	            dataSource.setValidationQuery(value);
	        }
	
	        value = properties.getProperty(PROP_ACCESSTOUNDERLYINGCONNECTIONALLOWED);
	        if (value != null) {
	            dataSource.setAccessToUnderlyingConnectionAllowed(Boolean.valueOf(value).booleanValue());
	        }
	
	        value = properties.getProperty(PROP_REMOVEABANDONED);
	//        value = "false";
	        if (value != null && value.equals("true")) {
	            dataSource.setRemoveAbandoned(Boolean.valueOf(value).booleanValue());
	            value = properties.getProperty(PROP_REMOVEABANDONEDTIMEOUT);
	            if (value != null) { 
	            	int v_ = Integer.parseInt(value);
	                dataSource.setRemoveAbandonedTimeout(v_);
	            }
	            
	            value = properties.getProperty(PROP_LOGABANDONED);
	            if (value != null) {
	                dataSource.setLogAbandoned(Boolean.valueOf(value).booleanValue());
	            }
	            value = properties.getProperty(PROP_TESTWHILEIDLE);
	            if(value != null)
	            {
	            	 dataSource.setTestWhileIdle(Boolean.valueOf(value).booleanValue());
	            }
	        }
	
	       
	
	       
	
	        value = properties.getProperty(PROP_POOLPREPAREDSTATEMENTS);
	        if (value != null) {
	            dataSource.setPoolPreparedStatements(Boolean.valueOf(value).booleanValue());
	        }
	
	        value = properties.getProperty(PROP_MAXOPENPREPAREDSTATEMENTS);
	        if (value != null) {
	            dataSource.setMaxOpenPreparedStatements(Integer.parseInt(value));
	        }
	        value = properties.getProperty(PROP_WHENEXHAUSTEDACTION);
	        if(value != null)
	        	dataSource.setWhenExhaustedAction(Byte.parseByte(value));
	        value = properties.getProperty(PROP_CONNECTIONPROPERTIES);
	        if (value != null) {
	          Properties p = getProperties(value);
	          Enumeration e = p.propertyNames();
	          while (e.hasMoreElements()) {
	            String propertyName = (String) e.nextElement();
	            dataSource.addConnectionProperty(propertyName, p.getProperty(propertyName));
	          }
	        }
	
	        // Return the configured DataSource instance
	        ds = dataSource;
    	}
    	else
    	{
    		NativeDataSource dataSource = new NativeDataSource();
    		 String value = null;
    		 value = properties.getProperty(PROP_PASSWORD);
 	        if (value != null) {
 	            dataSource.setPassword(value);
 	        }
 	
 	        value = properties.getProperty(PROP_URL);
 	        if (value != null) {
 	            dataSource.setUrl(value);
 	        }
 	
 	        value = properties.getProperty(PROP_USERNAME);
 	        if (value != null) {
 	            dataSource.setUser(value);
 	        }
 	        value = properties.getProperty(PROP_DEFAULTAUTOCOMMIT);
 	        if (value != null) {
 	            dataSource.setDefaultAutoCommit(Boolean.valueOf(value).booleanValue());
 	        }
 	
 	        value = properties.getProperty(PROP_DEFAULTREADONLY);
 	        if (value != null) {
 	            dataSource.setDefaultReadOnly(Boolean.valueOf(value).booleanValue());
 	        }
 	
 	        value = properties.getProperty(PROP_DEFAULTTRANSACTIONISOLATION);
 	        if (value != null) {
 	            int level = PoolableConnectionFactory.UNKNOWN_TRANSACTIONISOLATION;
 	            if ("NONE".equalsIgnoreCase(value)) {
 	                level = Connection.TRANSACTION_NONE;
 	            }
 	            else if ("READ_COMMITTED".equalsIgnoreCase(value)) {
 	                level = Connection.TRANSACTION_READ_COMMITTED;
 	            }
 	            else if ("READ_UNCOMMITTED".equalsIgnoreCase(value)) {
 	                level = Connection.TRANSACTION_READ_UNCOMMITTED;
 	            }
 	            else if ("REPEATABLE_READ".equalsIgnoreCase(value)) {
 	                level = Connection.TRANSACTION_REPEATABLE_READ;
 	            }
 	            else if ("SERIALIZABLE".equalsIgnoreCase(value)) {
 	                level = Connection.TRANSACTION_SERIALIZABLE;
 	            }
 	            else {
 	                try {
 	                    level = Integer.parseInt(value);
 	                } catch (NumberFormatException e) {
 	                    System.err.println("Could not parse defaultTransactionIsolation: " + value);
 	                    System.err.println("WARNING: defaultTransactionIsolation not set");
 	                    System.err.println("using default value of database driver");
 	                    level = PoolableConnectionFactory.UNKNOWN_TRANSACTIONISOLATION;
 	                }
 	            }
 	            dataSource.setDefaultTransactionIsolation(level);
 	        }
 	        
 	       value = properties.getProperty(PROP_CONNECTIONPROPERTIES);
	        if (value != null) {
	          Properties p = getProperties(value);
	          Enumeration e = p.propertyNames();
	          while (e.hasMoreElements()) {
	            String propertyName = (String) e.nextElement();
	            dataSource.addConnectionProperty(propertyName, p.getProperty(propertyName));
	          }
	        }
 	
 	        value = properties.getProperty(PROP_DEFAULTCATALOG);
 	        if (value != null) {
 	        	dataSource.setDefaultCatalog(value);
 	        }
 	        
 	       value = properties.getProperty(PROP_LOGABANDONED);
           if (value != null) {
               dataSource.setLogAbandoned(Boolean.valueOf(value).booleanValue());
           }
 	
 	        value = properties.getProperty(PROP_DRIVERCLASSNAME);
 	        if (value != null) {
 	        	dataSource.setDriver(value);
 	        }
    		ds = dataSource;
    	}
    	return ds;
    }

    /**
     * <p>Parse properties from the string. Format of the string must be [propertyName=property;]*<p>
     * @param propText
     * @return Properties
     * @throws Exception
     */
    static private Properties getProperties(String propText) throws Exception {
      Properties p = new Properties();
      if (propText != null) {
        p.load(new ByteArrayInputStream(propText.replace(';', '\n').getBytes()));
      }
      return p;
    }
}
